// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: chensili

#pragma once

#include <algorithm>
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include <glog/logging.h>
#include "coin/proto/coin_order_enums.pb.h"
#include "coin2/app/driver.h"
#include "coin2/base/log.h"
#include "coin2/exchange/base/api_util/auth_key.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/strategy/dmm_strat/dmm_strat_config.pb.h"
#include "coin2/strategy/order_executor/mm_executor.h"
#include "coin2/strategy/strategy.h"
#include "coin2/strategy/util/moving_ohlc.h"

namespace coin2::strategy::dmm_strat {

using coin::proto::OrderDirection;
using coin::proto::coin_executor::AggressiveExecutorConfig;
using ::coin2::app::impl::Driver;
using ::coin2::exchange::base::feed::FeedUpdate;
using ::coin2::exchange::base::market::MarketExchangeApi;
using ::coin2::exchange::base::order::IOrderSubsystem;
using ::coin2::exchange::base::order::OrderUpdate;
using ::coin2::exchange::base::symbology::IProductHolder;
using ::coin::proto::OrderEvent;
using ::coin2::strategy::order_executor::PassiveOrderExecutor;
using ::coin2::strategy::util::MovingHigh;
using ::coin2::strategy::util::MovingLow;

class DmmSubStrategy;

class AmplitudeChecker {
 public:
  AmplitudeChecker(const IProduct* product, const AmplitudeCheckerConfig& config);
  void onBookFeed(const std::string& product_key, const FeedUpdate& upd);
  bool GetAlarm() const { return alarm_; }
  void UpdateAlarm();
 private:
  std::string product_key_;
  const AmplitudeCheckerConfig config_;
  std::unique_ptr<MovingLow<double>> moving_low_;
  std::unique_ptr<MovingHigh<double>> moving_high_;
  double threshold_;
  bool alarm_{false};

  void SetAlarm();
  void ResetAlarm();
};

class DmmPricer {
 public:
  explicit DmmPricer(const IProduct* product, ::coin2::app::Driver*);
  void onBookFeed(const std::string& product_key, const FeedUpdate& upd);
  std::pair<double, double> CalculatePriceBySpread(double spread, bool bbo_protection);
  std::pair<double, double> CalculatePriceByEdge(
      double sell_edge, double buy_edge, bool bbo_protection);
  std::pair<double, double> AdjustPrice(
      double sell_price, double buy_price, bool bbo_protection);
  bool Ready() { return ready_; }
 private:
  std::function<const IProductHolder*(int64_t)> get_product_holder_;
  std::string product_key_;
  bool ready_{false};
  std::optional<int64_t> book_ts_;
  std::optional<PriceQty> bid0_;
  std::optional<PriceQty> ask0_;
};

class DmmStrategy : public ::coin2::strategy::IStrategy {
 public:
  DmmStrategy(const std::experimental::filesystem::path& strat_json,
              bool dry_run,
              std::optional<int64_t> timestamp,
              int exit_after_sec);
  void Init(::coin2::app::Driver* driver) override;
  void InitAuthKey(::coin2::app::Driver* driver);
  void InitReserveMap();
  void onBookFeed(const FeedUpdate& upd) override;
  void onAccountInfo(const OrderUpdate& upd) override;
  void onAccountOrder(const OrderUpdate& upd) override;
  void onOrderLog(const coin::proto::OrderGatewayLog& order_log) override;
  void onCleanup(Driver* driver) override;
  void InjectFeed(const coin2::feed::mgr::impl::FeedSubsystemManager* feed_mgr) final;
 private:
  int64_t strategy_created_time_;
  int64_t exit_after_ns_;
  DmmProductConfig config_;
  std::vector<std::unique_ptr<DmmSubStrategy>> dmm_sub_strats_;
  std::unordered_map<std::string, std::vector<DmmSubStrategy*>> feed_sub_map_;
  std::unordered_map<std::string, DmmSubStrategy*> order_sub_map_;
  std::unique_ptr<StrategyReporter2> strat_reporter_;
  std::unique_ptr<AuthKey> auth_key_;
};

class DmmSubStrategy : public ::coin2::strategy::IStrategy {
 public:
  using ::coin2::strategy::IStrategy::Init;
  using ::coin2::strategy::IStrategy::onBookFeed;
  DmmSubStrategy(
      DmmStrategy* parent,
      const DmmProductConfig& config,
      int64_t strategy_created_time,
      bool enable_telemetry,
      bool dry_run);
  MarketExchangeApi mea() { return MarketExchangeApi::FromString(config_.trade_mea()); }
  IOrderSubsystem* os() { return driver_->order()->GetUniqueSystem(mea()); }
  void Init(::coin2::app::Driver* driver,
            ::coin2::strategy::util::StrategyReporter2* reporter);
  const DmmProductConfig& GetConfig() const { return config_; }
  const IProduct* GetTradeProduct() const {return trade_product_.get(); }
  PassiveOrderExecutor* GetMutableExecutor() { return executor_.get(); }
  void CheckRolloverPeriod(int64_t timestamp);
  bool ReduceOnlyEnforced();
  void onAccountInfo(const OrderUpdate& upd) override;
  void onBookFeed(const std::string& product_key, const FeedUpdate& upd);
  void InjectFeed(const coin2::feed::mgr::impl::FeedSubsystemManager* feed_mgr) final;

 private:
  DmmStrategy* parent_{nullptr};
  DmmProductConfig config_;
  int64_t strategy_created_time_;
  std::unique_ptr<IProduct> trade_product_;
  std::unique_ptr<AuthKey> auth_key_;
  Driver* driver_;
  exchange::base::strategy_util::StrategyLogger* strat_logger_;
  exchange::base::strategy_util::PrometheusHealthReporter* health_reporter_;
  std::unique_ptr<PassiveOrderExecutor> executor_;
  std::unique_ptr<DmmPricer> pricer_;
  bool enable_telemetry_{false};
  bool dry_run_{false};
  bool bbo_protection_{true};
  std::unique_ptr<AmplitudeChecker> amplitude_checker_;
  std::optional<double> spread_;
  std::optional<double> spread_hard_;
  std::optional<double> reduce_only_with_position_threshold_;
  bool within_rollover_period_{false};
  AggressiveExecutorConfig exe_config_;
  int64_t last_rollover_check_ts_{0};
  int64_t last_amplitude_check_ts_{0};
  std::set<std::string> feed_keys_;
  const coin2::feed::mgr::impl::FeedSubsystemManager* feed_mgr_{nullptr};
};

}  // namespace coin2::strategy::dmm_strat
